﻿using System.Dynamic;
using System.Text;
using Dm.util;
using SqlSugar;
using WeDonekRpc.Helper;
using WeDonekRpc.Model;

namespace WeDonekRpc.SqlSugar.VirtualQuery
{
    internal class VirtualQuery : IVirtualQuery
    {
        private static readonly Dictionary<QueryFuncType, string> FuncType = new Dictionary<QueryFuncType, string>
        {
            { QueryFuncType.Min,"AggregateMin" },
            { QueryFuncType.Max,"AggregateMax" },
            { QueryFuncType.Sum, "AggregateSum" },
            { QueryFuncType.Count, "AggregateCount" },
            { QueryFuncType.DistinctCount, "AggregateDistinctCount" },
        };

        private readonly SqlSugarProvider _Provider;
        private ISugarQueryable<ExpandoObject> _Queryable;
        internal VirtualQuery ( SqlSugarProvider provider )
        {
            this._Provider = provider;
        }
        private int _id = 1;

        internal Dictionary<string, string> Table { get; } = new Dictionary<string, string>();

        internal string MainTable
        {
            get;
            private set;
        }
        private void _InitTable ( TableStruct table )
        {
            string key = "t" + this._id;
            this._id += 1;
            this.Table.Add(table.Name, key);
            this.MainTable = key;
            this._Queryable = this._Provider.Queryable(table.Name, key);
            if ( !table.Foreign.IsNull() )
            {
                List<JoinInfoParameter> joins = new List<JoinInfoParameter>();
                foreach ( TableStruct a in table.Foreign )
                {
                    this._InitTable(a, key, joins);
                }
                this._Queryable = this._Queryable.AddJoinInfo(joins);
            }
        }
        private void _InitTable ( TableStruct child, string table, List<JoinInfoParameter> joins )
        {
            string key = "t" + this._id;
            this._id += 1;
            this.Table.Add(child.Name, key);
            JoinInfoParameter param = new JoinInfoParameter
            {
                TableName = child.Name,
                ShortName = key,
                Type = child.JoinType
            };
            if ( child.Filter.Count == 1 )
            {
                KeyValuePair<string, string> one = child.Filter.First();
                param.Models = ObjectFuncModel.Create("Equals", table + "." + one.Value, key + "." + one.Key);
            }
            else
            {
                List<string> filter = new List<string>();
                child.Filter.ForEach(( i, val ) =>
                {
                    filter.Add(table + "." + val);
                    filter.Add("=");
                    filter.Add(key + "." + i);
                    filter.Add("&&");
                });
                filter.RemoveAt(filter.Count - 1);
                param.Models = ObjectFuncModel.Create("Format", filter.ToArray());
            }
            joins.Add(param);
            if ( !child.Foreign.IsNull() )
            {
                foreach ( TableStruct a in child.Foreign )
                {
                    this._InitTable(a, key, joins);
                }
            }
        }
        #region Where
        private void _LoadWhere ( IWhereCol[] where )
        {
            List<IConditionalModel> list = new List<IConditionalModel>(where.Length);
            if ( where.Length == 1 )
            {
                IWhereCol col = where[0];
                IConditionalModel t = this._LoadWhere(where[0]);
                list.Add(t);
            }
            else
            {
                where.ForEach(c =>
                {
                    if ( c is IWhereCollect i )
                    {
                        list.Add(this._LoadWhere(c));
                    }
                    else
                    {
                        list.Add(this._LoadWhere(c));
                    }
                });
            }
            this._Queryable = this._Queryable.Where(list);
        }
        private IConditionalModel _LoadWhere ( IWhereCol where )
        {
            if ( where is IWhereCollect i )
            {
                ConditionalCollections collects = new ConditionalCollections
                {
                    ConditionalList = new List<KeyValuePair<WhereType, ConditionalModel>>()
                };
                i.Collect.ForEach(( c, i ) =>
                {
                    ConditionalModel t = new ConditionalModel
                    {
                        ConditionalType = c.ConditionalType,
                        FieldName = c.Name,
                        CustomConditionalFunc = new ConditionalFunc(c, this)
                    };
                    collects.ConditionalList.Add(new KeyValuePair<WhereType, ConditionalModel>(c.IsAnd ? WhereType.And : WhereType.Or, t));
                });
                return collects;
            }
            else
            {
                WhereCol t = (WhereCol)where;
                return new ConditionalModel
                {
                    ConditionalType = t.ConditionalType,
                    FieldName = t.Name,
                    CustomConditionalFunc = new ConditionalFunc(t, this)
                };
            }
        }
        #endregion

        private void _InitSelect ( SelectStruct[] selects )
        {
            if ( selects.IsNull() )
            {
                throw new ErrorException("db.virtual.select.null");
            }
            List<SelectModel> list = new List<SelectModel>(selects.Length);
            selects.ForEach(c =>
            {
                SelectModel t = new SelectModel
                {
                    AsName = c.Alias.IsNull() ? c.Name : c.Alias,
                };
                string name = c.Name;
                if ( c.Table != null )
                {
                    name = this.Table[c.Table] + "." + name;
                }
                if ( c.Func == QueryFuncType.None )
                {
                    t.FieldName = name;
                }
                else if ( c.Func == QueryFuncType.DateTimeFormat )
                {
                    t.FieldName = ObjectFuncModel.Create("ToString", name, "{string}:" + c.Format);
                }
                else if ( c.Func == QueryFuncType.DateValue )
                {
                    t.FieldName = ObjectFuncModel.Create("DateValue", name, "{string}:" + c.DateType.Value);
                }
                else
                {
                    t.FieldName = ObjectFuncModel.Create(FuncType[c.Func], name);
                }
                list.Add(t);
            });
            this._Queryable = this._Queryable.Select(list);
        }

        private void _InitGroupBy ( GroupByStruct[] groupBy )
        {
            List<GroupByModel> list = new List<GroupByModel>(groupBy.Length);
            groupBy.ForEach(c =>
            {
                object name = c.Table != null ? this.Table[c.Table] + "." + c.Name : c.Name;
                if ( c.Func == QueryFuncType.DateValue )
                {
                    name = ObjectFuncModel.Create("DateValue", name, "{string}:" + c.DateType.Value);
                }
                else if ( c.Func == QueryFuncType.DateTimeFormat )
                {
                    name = ObjectFuncModel.Create("ToString", name, "{string}:" + c.Format);
                }
                else if ( c.Func != QueryFuncType.None )
                {
                    name = ObjectFuncModel.Create(FuncType[c.Func], name);
                }
                list.Add(new GroupByModel
                {
                    FieldName = name,
                });
            });
            this._Queryable = this._Queryable.GroupBy(list);
        }


        private void _LoadHaving ( HavingCol where )
        {
            if ( where.Value is IFuncModel i )
            {
                this._Queryable = this._Queryable.Having(i);
                return;
            }
            string name = where.Name;
            if ( where.Table != null )
            {
                name = this.Table[name] + "." + name;
            }
            if ( where.Value == null )
            {
                this._Queryable = this._Queryable.Having(ObjectFuncModel.Create("HasValue", name));
            }
            else if ( where.ConditionalType == ConditionalType.In || where.ConditionalType == ConditionalType.NotIn )
            {
                IFuncModel model = ObjectFuncModel.Create("ContainsArray", this._FormatVal((Array)where.Value, where.DbType), name);
                if ( where.ConditionalType == ConditionalType.In )
                {
                    this._Queryable = this._Queryable.Having(model);
                }
                else
                {
                    this._Queryable = this._Queryable.Having(ObjectFuncModel.Create("format", "not", "(", model, ")"));
                }
            }
            else if ( where.Func == QueryFuncType.None )
            {
                IFuncModel model = ObjectFuncModel.Create(where.ConditionalType.ToString(), name, this._FormatVal(where));
                this._Queryable = this._Queryable.Having(model);
            }
            else if ( where.Func == QueryFuncType.DateTimeFormat )
            {
                IFuncModel func = ObjectFuncModel.Create("ToString", name, "{string}:" + where.Format);
                IFuncModel model = ObjectFuncModel.Create(where.ConditionalType.ToString(), func, this._FormatVal(where));
                this._Queryable = this._Queryable.Having(model);
            }
            else if ( where.Func == QueryFuncType.DateValue )
            {
                IFuncModel func = ObjectFuncModel.Create("DateValue", name, "{string}:" + where.DateType.Value);
                IFuncModel model = ObjectFuncModel.Create(where.ConditionalType.ToString(), func, this._FormatVal(where));
                this._Queryable = this._Queryable.Having(model);
            }
            else
            {
                IFuncModel func = ObjectFuncModel.Create(FuncType[where.Func], name);
                IFuncModel model = ObjectFuncModel.Create(where.ConditionalType.ToString(), func, this._FormatVal(where));
                this._Queryable = this._Queryable.Having(model);
            }
        }
        private object _FormatVal ( Array val, System.Data.DbType type )
        {
            Array list = val;
            if ( type == System.Data.DbType.String )
            {
                StringBuilder str = new StringBuilder("{string}:[");
                foreach ( object c in list )
                {
                    _ = str.Append('\"');
                    _ = str.Append(c);
                    _ = str.Append('\"');
                    _ = str.Append(',');
                }
                _ = str.Remove(str.Length - 1, 1);
                _ = str.append("]");
                return str.ToString();
            }
            else
            {
                string head = _GetTypeName(type);
                StringBuilder str = new StringBuilder(head);
                _ = str.append(":[");
                foreach ( object c in list )
                {
                    _ = str.Append(c);
                    _ = str.Append(',');
                }
                _ = str.Remove(str.Length - 1, 1);
                _ = str.append("]");
                return str.ToString();
            }
        }
        private object _FormatVal ( HavingCol col )
        {
            string head = _GetTypeName(col.DbType);
            return head + ":" + col.Value.toString();
        }
        private static string _GetTypeName ( System.Data.DbType type )
        {
            if ( type == System.Data.DbType.Decimal )
            {
                return "{decimal}";
            }
            else if ( type == System.Data.DbType.Int64 )
            {
                return "{long}";
            }
            else if ( type == System.Data.DbType.String )
            {
                return "{string}";
            }
            else if ( type == System.Data.DbType.DateTime || type == System.Data.DbType.Date )
            {
                return "{DateTime}";
            }
            else if ( type == System.Data.DbType.Boolean )
            {
                return "{bool}";
            }
            else if ( type == System.Data.DbType.Int16 )
            {
                return "{short}";
            }
            else if ( type == System.Data.DbType.Double )
            {
                return "{double}";
            }
            else if ( type == System.Data.DbType.Single )
            {
                return "{float}";
            }
            return "{int}";
        }
        internal void Load ( QueryStruct body )
        {
            this._InitTable(body.Table);
            if ( !body.Where.IsNull() )
            {
                this._LoadWhere(body.Where);
            }
            if ( !body.GroupBy.IsNull() )
            {
                this._InitGroupBy(body.GroupBy);
                if ( body.Having.HasValue )
                {
                    this._LoadHaving(body.Having.Value);
                }
            }
            this._InitOrderBy(body.OrderBy);
            this._InitSelect(body.Select);
        }
        private void _InitOrderBy ( OrderByStruct[] orderBy )
        {
            if ( orderBy.IsNull() )
            {
                return;
            }
            List<OrderByModel> list = OrderByModel.Create(orderBy.ConvertAll(c => new OrderByModel
            {
                FieldName = c.Name,
                OrderByType = c.IsDesc ? OrderByType.Desc : OrderByType.Asc
            }));
            this._Queryable = this._Queryable.OrderBy(list);
        }
        public ExpandoObject Get ()
        {
            return this._Queryable.First();
        }
        public ExpandoObject[] Gets ()
        {
            return this._Queryable.ToArray();
        }
        public ExpandoObject[] Gets ( string orderBy )
        {
            return this._Queryable.AS("at").OrderBy(orderBy).ToArray();
        }
        public ExpandoObject[] Query ( IBasicPage paging, out int count )
        {
            count = this._Queryable.Clone().Count();
            if ( paging.OrderBy != null )
            {
                return this._Queryable.AS("at").OrderBy(paging.OrderBy).Skip(( paging.Index - 1 ) * paging.Size).Take(paging.Size).ToArray();
            }
            return this._Queryable.AS("at").Skip(( paging.Index - 1 ) * paging.Size).Take(paging.Size).ToArray();
        }
        public ExpandoObject Get ( string orderBy )
        {
            return this._Queryable.AS("a").OrderBy(orderBy).First();
        }

        public string ToSql ()
        {
            return this._Queryable.ToSqlString();
        }
    }
}
